home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
AM/FM: Amiga Musicians' Freeware Magazine 2
/
AM-FM 2.adf
/
Utilities
/
MFT
/
midifile.c
< prev
next >
Wrap
C/C++ Source or Header
|
1991-09-21
|
10KB
|
535 lines
/*
* Read a Standard MIDI File. Externally-assigned function pointers are
* called upon recognizing things in the file. See midifile(3).
*/
#include "midi.h"
#include "midifile.h"
#define EOF (-1)
#define NOARGS VOID
#define VOID void
/* public stuff */
/* Functions to be called while processing the MIDI file. */
VOID (*Mf_starttrack)(NOARGS) = 0;
VOID (*Mf_endtrack)(NOARGS) = 0;
int (*Mf_getc)(NOARGS) = 0;
VOID (*Mf_eot)(NOARGS) = 0;
VOID (*Mf_error)(char *) = 0;
VOID (*Mf_header)(int,int,int) = 0;
VOID (*Mf_on)(int,int,int) = 0;
VOID (*Mf_off)(int,int,int) = 0;
VOID (*Mf_pressure)(int,int,int) = 0;
VOID (*Mf_controller)(int,int,int) = 0;
VOID (*Mf_pitchbend)(int,int,int) = 0;
VOID (*Mf_program)(int,int) = 0;
VOID (*Mf_chanpressure)(int,int) = 0;
VOID (*Mf_sysex)(int,char*) = 0;
VOID (*Mf_arbitrary)(int,char*) = 0;
VOID (*Mf_metamisc)(int,int,char*) = 0;
VOID (*Mf_seqnum)(int) = 0;
VOID (*Mf_smpte)(int,int,int,int,int) = 0;
VOID (*Mf_timesig)(int,int,int,int) = 0;
VOID (*Mf_tempo)(int) = 0;
VOID (*Mf_keysig)(int,int) = 0;
VOID (*Mf_sqspecific)(int,char*) = 0;
VOID (*Mf_text)(int,int,char*) = 0;
int Mf_nomerge = 0; /* 1 => continue'ed system exclusives are */
/* not collapsed. */
long Mf_currtime = 0L; /* current time in delta-time units */
int Mf_skipinit = 0; /* 1 if initial garbage should be skipped */
/* private stuff */
static long Mf_toberead = 0L;
static long readvarinum(NOARGS);
static long read32bit(NOARGS);
static int read16bit(NOARGS);
static VOID msgenlarge(NOARGS);
static char *msg(NOARGS);
static int readheader(NOARGS);
static VOID readtrack(NOARGS);
static VOID sysex(NOARGS), msginit(NOARGS);
static int egetc(NOARGS);
static int msgleng(NOARGS);
static int readmt(char*,int);
static long to32bit(int,int,int,int);
static int to16bit(int,int);
static VOID mferror(char *);
static VOID badbyte(int);
static VOID metaevent(int);
static VOID msgadd(int);
static VOID chanmessage(int,int,int);
/*----- midifile -----*/
VOID midifile() /* The only non-static function in this file. */
{
int ntrks;
if ( Mf_getc == 0 )
mferror("midifile() called without setting Mf_getc");
ntrks = readheader();
if ( ntrks <= 0 )
mferror("No tracks!");
while ( ntrks-- > 0 )
readtrack();
}
static int
readmt(s,skip) /* read through the "MThd" or "MTrk" header string */
char *s;
int skip; /* if 1, we attempt to skip initial garbage */
{
int nread = 0;
char b[4];
char buff[32];
int c;
char *errmsg = "expecting ";
retry:
while ( nread<4 ) {
c = (*Mf_getc)();
if ( c == EOF ) {
errmsg = "EOF while expecting ";
goto err;
}
b[nread++] = c;
}
/* See if we found the 4 characters we're looking for */
if ( s[0]==b[0] && s[1]==b[1] && s[2]==b[2] && s[3]==b[3] )
return(0);
if ( skip ) {
/* If we are supposed to skip initial garbage, */
/* try again with the next character. */
b[0]=b[1];
b[1]=b[2];
b[2]=b[3];
nread = 3;
goto retry;
}
err:
(VOID) strcpy(buff,errmsg);
(VOID) strcat(buff,s);
mferror(buff);
return(0);
}
static int
egetc() /* to read a single character and abort on EOF */
{
int c = (*Mf_getc)();
if ( c == EOF )
error("premature EOF");
Mf_toberead--;
return(c);
}
static int
readheader() /* read a header chunk */
{
int format, ntrks, division;
if ( readmt("MThd",Mf_skipinit) == EOF )
return(0);
Mf_toberead = read32bit();
format = read16bit();
ntrks = read16bit();
division = read16bit();
if ( Mf_header )
(*Mf_header)(format,ntrks,division);
/* flush any extra stuff, in case the length of header is not 6 */
while ( Mf_toberead > 0 )
(VOID) egetc();
return(ntrks);
}
static VOID
readtrack() /* read a track chunk */
{
/* This array is indexed by the high half of a status byte. It's */
/* value is either the number of bytes needed (1 or 2) for a channel */
/* message, or 0 (meaning it's not a channel message). */
static int chantype[] = {
0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 through 0x70 */
2, 2, 2, 2, 1, 1, 2, 0 /* 0x80 through 0xf0 */
};
long lookfor, lng;
int c, c1, type;
int sysexcontinue = 0; /* 1 if last message was an unfinished sysex */
int running = 0; /* 1 when running status used */
int status = 0; /* (possibly running) status byte */
int needed;
if ( readmt("MTrk",0) == EOF )
return;
Mf_toberead = read32bit();
Mf_currtime = 0;
if ( Mf_starttrack )
(*Mf_starttrack)();
while ( Mf_toberead > 0 ) {
Mf_currtime += readvarinum(); /* delta time */
c = egetc();
if ( sysexcontinue && c != 0xf7 )
mferror("didn't find expected continuation of a sysex");
if ( (c & 0x80) == 0 ) { /* running status? */
if ( status == 0 )
mferror("unexpected running status");
running = 1;
}
else {
status = c;
running = 0;
}
needed = chantype[ (status>>4) & 0xf ];
if ( needed ) { /* ie. is it a channel message? */
if ( running )
c1 = c;
else
c1 = egetc() & 0x7f;
/* The &0x7f here may seem unnecessary, but I've seen */
/* 'bad' midi files that had, some, volume bytes */
/* with the upper bit set. This code should not harm */
/* proper data. */
chanmessage( status, c1, (needed>1) ? (egetc()&0x7f) : 0 );
continue;
}
switch ( c ) {
case 0xff: /* meta event */
type = egetc();
/* watch out - Don't combine the next 2 statements */
lng = readvarinum();
lookfor = Mf_toberead - lng;
msginit();
while ( Mf_toberead > lookfor )
msgadd(egetc());
metaevent(type);
break;
case 0xf0: /* start of system exclusive */
/* watch out - Don't combine the next 2 statements */
lng = readvarinum();
lookfor = Mf_toberead - lng;
msginit();
msgadd(0xf0);
while ( Mf_toberead > lookfor )
msgadd(c=egetc());
if ( c==0xf7 || Mf_nomerge==0 )
sysex();
else
sysexcontinue = 1; /* merge into next msg */
break;
case 0xf7: /* sysex continuation or arbitrary stuff */
/* watch out - Don't combine the next 2 statements */
lng = readvarinum();
lookfor = Mf_toberead - lng;
if ( ! sysexcontinue )
msginit();
while ( Mf_toberead > lookfor )
msgadd(c=egetc());
if ( ! sysexcontinue ) {
if ( Mf_arbitrary )
(*Mf_arbitrary)(msgleng(),msg());
}
else if ( c == 0xf7 ) {
sysex();
sysexcontinue = 0;
}
break;
default:
badbyte(c);
break;
}
}
if ( Mf_endtrack )
(*Mf_endtrack)();
return;
}
static VOID
badbyte(c)
int c;
{
char buff[32];
(VOID) sprintf(buff,"unexpected byte: 0x%02x",c);
mferror(buff);
}
static VOID
metaevent(type)
{
int leng = msgleng();
char *m = msg();
switch ( type ) {
case 0x00:
if ( Mf_seqnum )
(*Mf_seqnum)(to16bit(m[0],m[1]));
break;
case 0x01: /* Text event */
case 0x02: /* Copyright notice */
case 0x03: /* Sequence/Track name */
case 0x04: /* Instrument name */
case 0x05: /* Lyric */
case 0x06: /* Marker */
case 0x07: /* Cue point */
case 0x08:
case 0x09:
case 0x0a:
case 0x0b:
case 0x0c:
case 0x0d:
case 0x0e:
case 0x0f:
/* These are all text events */
if ( Mf_text )
( Mf_text)(type,leng,m);
break;
case 0x2f: /* End of Track */
if ( Mf_eot )
(*Mf_eot)();
break;
case 0x51: /* Set tempo */
if ( Mf_tempo )
(*Mf_tempo)(to32bit(0,m[0],m[1],m[2]));
break;
case 0x54:
if ( Mf_smpte )
(*Mf_smpte)(m[0],m[1],m[2],m[3],m[4]);
break;
case 0x58:
if ( Mf_timesig )
(*Mf_timesig)(m[0],m[1],m[2],m[3]);
break;
case 0x59:
if ( Mf_keysig )
(*Mf_keysig)(m[0],m[1]);
break;
case 0x7f:
if ( Mf_sqspecific )
(*Mf_sqspecific)(leng,m);
break;
default:
if ( Mf_metamisc )
(*Mf_metamisc)(type,leng,m);
}
}
static VOID
sysex()
{
if ( Mf_sysex )
(*Mf_sysex)(msgleng(),msg());
}
static VOID chanmessage(status,c1,c2)
int status;
int c1, c2;
{
int chan = status & 0xf;
switch ( status & 0xf0 ) {
case NOTEOFF:
if ( Mf_off )
(*Mf_off)(chan,c1,c2);
break;
case NOTEON:
if ( Mf_on )
(*Mf_on)(chan,c1,c2);
break;
case PRESSURE:
if ( Mf_pressure )
(*Mf_pressure)(chan,c1,c2);
break;
case CONTROLLER:
if ( Mf_controller )
(*Mf_controller)(chan,c1,c2);
break;
case PITCHBEND:
if ( Mf_pitchbend )
(*Mf_pitchbend)(chan,c1,c2);
break;
case PROGRAM:
if ( Mf_program )
(*Mf_program)(chan,c1);
break;
case CHANPRESSURE:
if ( Mf_chanpressure )
(*Mf_chanpressure)(chan,c1);
break;
}
}
/* readvarinum - read a varying-length number, and return the */
/* number of characters it took. */
static long
readvarinum()
{
long value;
int c;
c = egetc();
value = c;
if ( c & 0x80 ) {
value &= 0x7f;
do {
c = egetc();
value = (value << 7) + (c & 0x7f);
} while (c & 0x80);
}
return (value);
}
static long
to32bit(c1,c2,c3,c4)
{
long value = 0L;
value = (c1 & 0xff);
value = (value<<8) + (c2 & 0xff);
value = (value<<8) + (c3 & 0xff);
value = (value<<8) + (c4 & 0xff);
return (value);
}
static
to16bit(c1,c2)
int c1, c2;
{
return ((c1 & 0xff ) << 8) + (c2 & 0xff);
}
static long
read32bit()
{
int c1, c2, c3, c4;
c1 = egetc();
c2 = egetc();
c3 = egetc();
c4 = egetc();
return to32bit(c1,c2,c3,c4);
}
static int
read16bit()
{
int c1, c2;
c1 = egetc();
c2 = egetc();
return to16bit(c1,c2);
}
static VOID
mferror(s)
char *s;
{
if ( Mf_error )
(*Mf_error)(s);
else
exit(1);
}
/* The code below allows collection of a system exclusive message of */
/* arbitrary length. The Msgbuff is expanded as necessary. The only */
/* visible data/routines are msginit(), msgadd(), msg(), msgleng(). */
#define MSGINCREMENT 128
static char *Msgbuff = 0; /* message buffer */
static int Msgsize = 0; /* Size of currently allocated Msg */
static int Msgindex = 0; /* index of next available location in Msg */
static VOID
msginit()
{
Msgindex = 0;
}
static char *
msg()
{
return(Msgbuff);
}
static int
msgleng()
{
return(Msgindex);
}
static VOID
msgadd(c)
int c;
{
/* if necessary,relocate larger message buffer. */
if ( Msgindex >= Msgsize )
msgenlarge();
Msgbuff[Msgindex++] = c;
}
static VOID
msgenlarge()
{
char *newmess;
char *oldmess = Msgbuff;
int oldleng = Msgsize;
char *malloc();
Msgsize += MSGINCREMENT;
newmess = malloc( (unsigned)(sizeof(char)*Msgsize) );
/* copy old message into larger new one */
if ( oldmess != 0 ) {
register char *p = newmess;
register char *q = oldmess;
register char *endq = &oldmess[oldleng];
for ( ; q!=endq ; p++,q++ )
*p = *q;
free(oldmess);
}
Msgbuff = newmess;
}